﻿/*
 * LINQ to LDAP
 * http://linqtoldap.codeplex.com/
 * 
 * Copyright Alan Hatter (C) 2010-2012
 * alan.hatter@gmail.com - http://hattercoding.blogspot.com/
 * 
 * This project is subject to licensing restrictions. Visit http://linqtoldap.codeplex.com/license for more information.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.DirectoryServices.Protocols;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;

namespace LinqToLdap.Collections
{
    /// <summary>
    /// Allows you to get or set values for an entry.
    /// </summary>
    public class DirectoryAttributes : IDirectoryAttributes
    {
        internal static readonly Dictionary<string, string> ByteProperties =
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                {
                    {"Ibm-entryuuid", "Ibm-entryuuid"},
                    {"Nsuniqueid", "Nsuniqueid"}
                };

        private readonly string _distinguishedName;
        private readonly SearchResultEntry _entry;
        private Dictionary<string, DirectoryAttributeModification> _changedAttributes;
        private ReadOnlyCollection<string> _attributeNames;
        private int _position = -1;

        /// <summary>
        /// Instantiates this class with a reference to <paramref name="entry"/>.
        /// </summary>
        /// <param name="entry">The <see cref="SearchResultEntry"/> to wrap.</param>
        internal DirectoryAttributes(SearchResultEntry entry)
        {
            if (entry == null) throw new ArgumentNullException("entry");
            _entry = entry;
        }

        /// <summary>
        /// Instantiates this class without a reference to a <see cref="SearchResultEntry"/>.  
        /// It can be used to set attributes for the directory.
        /// </summary>
        /// <param name="distinguishedName">
        /// The distinguished name for the entry.
        /// </param>
        /// <param name="attributes">
        /// THe attributes for the entry that will be treated as changed attributes.  Attributes named distinguishedname, cn, entrydn, and ou are ignored.
        /// </param>
        public DirectoryAttributes(string distinguishedName = null, IEnumerable<KeyValuePair<string, object>> attributes = null)
        {
            _distinguishedName = distinguishedName;

            if (attributes == null) return;

            foreach (var keyValuePair in attributes)
            {
                var name = keyValuePair.Key;
                if (name.Equals("distinguishedname", StringComparison.OrdinalIgnoreCase) || name.Equals("cn", StringComparison.OrdinalIgnoreCase) ||
                    name.Equals("entrydn", StringComparison.OrdinalIgnoreCase) || name.Equals("ou", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                AddChanged(name, keyValuePair.Value.ToDirectoryModification(name, DirectoryAttributeOperation.Replace));
            }
        }

        /// <summary>
        /// All of the attributes pressent for the entry.
        /// </summary>
        public ReadOnlyCollection<string> AttributeNames
        {
            get 
            {
                return _attributeNames ??
                    (_entry != null 
                        ? _attributeNames = new ReadOnlyCollection<string>(_entry.Attributes.AttributeNames.Cast<string>().ToList()) 
                        : new ReadOnlyCollection<string>(new List<string>()));
            }
        }

        private void AddChanged(string name, DirectoryAttributeModification value)
        {
            if (name.Equals("distinguishedname", StringComparison.OrdinalIgnoreCase) || name.Equals("cn", StringComparison.OrdinalIgnoreCase) ||
                name.Equals("entrydn", StringComparison.OrdinalIgnoreCase) || name.Equals("ou", StringComparison.OrdinalIgnoreCase))
                throw new ArgumentException("Cannot change the distinguished name, common name, or organizational unit name. Please use MoveEntry or RenameEntry on DirectoryContext");

            if (_changedAttributes == null)
            {
                _changedAttributes = new Dictionary<string, DirectoryAttributeModification>(StringComparer.OrdinalIgnoreCase);
            }

            _changedAttributes[name] = value;
        }

        /// <summary>
        /// Gets the Distinguished Name
        /// </summary>
        public string DistinguishedName
        {
            get 
            { 
                return _entry != null 
                    ? _entry.DistinguishedName 
                    : (_distinguishedName.IsNullOrEmpty() ? string.Empty : _distinguishedName); 
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through the attributes.
        /// </summary>
        /// <returns></returns>
        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
        {
            return this;
        }

        /// <summary>
        /// Returns an enumerator that iterates through the attributes.
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this;
        }

        /// <summary>
        /// This class has no disposable references so Dispose is an empty implementation.
        /// </summary>
        public void Dispose(){}

        /// <summary>
        /// Advances the enumerator to the next element of the collection.
        /// </summary>
        /// <returns></returns>
        public bool MoveNext()
        {
            _position++;
            return _entry != null 
                ? _position < _entry.Attributes.Count
                : false;
        }

        /// <summary>
        /// Sets the enumerator to its initial position, which is before the first element in the collection.
        /// </summary>
        public void Reset()
        {
            _position = -1;
        }

        /// <summary>
        /// Gets the element in the collection at the current position of the enumerator.
        /// </summary>
        public KeyValuePair<string, object> Current
        {
            get
            {
                var attribute = AttributeNames[_position];
                return new KeyValuePair<string, object>(attribute, GetValue(attribute));
            }
        }

        /// <summary>
        /// Gets the element in the collection at the current position of the enumerator.
        /// </summary>
        object IEnumerator.Current
        {
            get { return Current; }
        }

        /// <summary>
        /// Gets the <see cref="DirectoryAttribute"/> if available.
        /// </summary>
        /// <param name="attribute"></param>
        /// <returns></returns>
        public DirectoryAttribute Get(string attribute)
        {
            if (string.IsNullOrWhiteSpace(attribute)) throw new ArgumentNullException("attribute");
            return _entry != null ? _entry.Attributes[attribute] : null;
        }

        /// <summary>
        /// Gets the data for the attribute, if available, without regard for performance.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public object GetValue(string attribute)
        {
            var directoryAttribute = Get(attribute);
            
            if (directoryAttribute != null)
            {
                if (directoryAttribute.Count > 1)
                {
                    var type = directoryAttribute[0].GetType();

                    if (type == typeof(string))
                    {
                        return GetStrings(attribute);
                    }
                    if (type == typeof(byte[]))
                    {
                        return GetByteArrays(attribute);
                    }
                }

                if (attribute.EndsWith("guid", StringComparison.OrdinalIgnoreCase) || ByteProperties.ContainsKey(attribute))
                {
                    var bytes = GetBytes(attribute);

                    if (bytes != null && bytes.Length == 16)
                    {
                        return new Guid(bytes);
                    }

                    return bytes;
                }
                if ("objectsid".Equals(attribute, StringComparison.OrdinalIgnoreCase))
                {
                    return GetSecurityIdentifier(attribute);
                }
                if ("photo".Equals(attribute, StringComparison.OrdinalIgnoreCase))
                {
                    return GetBytes(attribute);
                }

                return directoryAttribute.Count == 0 ? null : directoryAttribute[0];
            }

            return null;
        }

        /// <summary>
        /// Gets the data for the attribute, if available, without regard for performance.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public object this[string attribute]
        {
            get { return GetValue(attribute); }
        }

        /// <summary>
        /// Gets the byte array value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public byte[] GetBytes(string attribute)
        {
            byte[] value = null;
            var type = typeof (byte[]);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(type);
                    rawValue = valueArray.Length == 0
                                   ? null
                                   : valueArray[0];
                    value = rawValue == null
                        ? default(byte[])
                        : (byte[])rawValue;
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, rawValue, type);
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the <see cref="X509Certificate2"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public X509Certificate2 GetX509Certificate2(string attribute)
        {
            X509Certificate2 value = null;
            var type = typeof(byte[]);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(type);
                    rawValue = valueArray.Length == 0
                                   ? null
                                   : valueArray[0];
                    value = rawValue == null
                        ? default(X509Certificate2)
                        : new X509Certificate2((byte[])rawValue);
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, rawValue, type);
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the <see cref="X509Certificate"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public X509Certificate GetX509Certificate(string attribute)
        {
            return GetX509Certificate2(attribute);
        }

        /// <summary>
        /// Gets the <see cref="String"/> array value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public string[] GetStrings(string attribute)
        {
            string[] value = null;
            var type = typeof(string);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object[] rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(type);
                    rawValue = valueArray.Length == 0
                                   ? null
                                   : valueArray;
                    value = rawValue == null
                        ? default(string[])
                        : Array.ConvertAll(rawValue, o => (string)o);
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, rawValue, type);
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the string value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public string GetString(string attribute)
        {
            string value = null;
            var type = typeof(string);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(type);
                    rawValue = valueArray.Length == 0
                                   ? null
                                   : valueArray[0];
                    value = rawValue == null
                        ? default(string)
                        : (string)rawValue;
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, rawValue, type);
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the <see cref="Int32"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public int? GetInt(string attribute)
        {
            return GetValueType<int>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="Int64"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public long? GetLong(string attribute)
        {
            return GetValueType<long>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="Double"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public double? GetDouble(string attribute)
        {
            return GetValueType<double>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="Decimal"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public decimal? GetDecimal(string attribute)
        {
            return GetValueType<decimal>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="Int16"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public short? GetShort(string attribute)
        {
            return GetValueType<short>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="Byte"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        /// <exception cref="LinqToLdap.Exceptions.MappingException">
        /// Thrown if the value cannot be 
        /// </exception>
        public byte? GetByte(string attribute)
        {
            return GetValueType<byte>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="float"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public float? GetFloat(string attribute)
        {
            return GetValueType<float>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="Boolean"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public bool? GetBoolean(string attribute)
        {
            return GetValueType<bool>(attribute);
        }

        /// <summary>
        /// Gets the <see cref="DateTime"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <param name="format">The format of the DateTime.  Use null for file time</param>
        /// <returns></returns>
        public DateTime? GetDateTime(string attribute, string format)
        {
            DateTime? value = null;
            var type = typeof(DateTime);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(typeof(string));
                    rawValue = valueArray.Length == 0
                                   ? null
                                   : valueArray[0];
                    value = rawValue == null || Equals("9223372036854775807", rawValue)
                        ? default(DateTime?)
                        : (format == null ? DateTime.FromFileTime(long.Parse(rawValue.ToString())) : rawValue.FormatLdapDateTime(format));
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, rawValue, type);
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the <see cref="DateTime"/> value for the specified attribute if available.
        /// Uses the default format yyyyMMddHHmmss.0Z.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public DateTime? GetDateTime(string attribute)
        {
            return GetDateTime(attribute, ExtensionMethods.LdapFormat);
        }

        /// <summary>
        /// Gets the list of <see cref="byte"/> array values for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public IEnumerable<byte[]> GetByteArrays(string attribute)
        {
            byte[][] value = null;
            var type = typeof(byte[]);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(type);
                    rawValue = valueArray;
                    value = new byte[valueArray.Length][];
                    for (int i = 0; i < valueArray.Length; i++)
                    {
                        value[i] = (byte[])valueArray[i];
                    }
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, value == null ? "" : string.Join(",", rawValue), type);
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the list of <see cref="X509Certificate2"/> values for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public IEnumerable<X509Certificate2> GetX509Certificate2s(string attribute)
        {
            X509Certificate2[] value = null;
            var type = typeof(byte[]);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(type);
                    rawValue = valueArray;
                    value = new X509Certificate2[valueArray.Length];
                    for (int i = 0; i < valueArray.Length; i++)
                    {
                        value[i] = new X509Certificate2((byte[])valueArray[i]);
                    }
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, value == null ? "" : string.Join(",", rawValue), type);
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the list of <see cref="X509Certificate"/> values for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public IEnumerable<X509Certificate> GetX509Certificates(string attribute)
        {
            return GetX509Certificate2s(attribute);
        }

        /// <summary>
        /// Gets the <see cref="Guid"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public Guid? GetGuid(string attribute)
        {
            byte[] value = null;
            try
            {
                value = GetBytes(attribute);
                if (value != null)
                {
                    return new Guid(value);
                }
            }
            catch (Exception ex)
            {
                ThrowFormatException(ex, attribute, value == null ? "" : string.Join(",", value), typeof(Guid));
            }

            return null;
        }

        /// <summary>
        /// Gets the <see cref="SecurityIdentifier"/> value for the specified attribute if available.
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <returns></returns>
        public SecurityIdentifier GetSecurityIdentifier(string attribute)
        {
            byte[] value = null;
            try
            {
                value = GetBytes(attribute);
                if (value != null)
                {
                    return new SecurityIdentifier(value, 0);
                }
            }
            catch (Exception ex)
            {
                ThrowFormatException(ex, attribute, value == null ? "" : string.Join(",", value), typeof(SecurityIdentifier));
            }

            return null;
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, byte[] value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, X509Certificate value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, IEnumerable<X509Certificate> value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, IEnumerable<X509Certificate2> value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, IEnumerable<string> value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, string value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, byte value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, short value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, int value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, long value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, float value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, double value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, decimal value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        /// <param name="format">The format of the <paramref name="value"/>.</param>
        public void Set(string attribute, DateTime value, string format = ExtensionMethods.LdapFormat)
        {
            var modification = new DirectoryAttributeModification { Name = attribute, Operation = DirectoryAttributeOperation.Replace };

            modification.Add(format == null ? value.ToFileTime().ToString() : value.FormatLdapDateTime(format));
            AddChanged(attribute, modification);
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, bool value)
        {
            var modification = new DirectoryAttributeModification { Name = attribute, Operation = DirectoryAttributeOperation.Replace };

            modification.Add(value ? "TRUE" : "FALSE");
            AddChanged(attribute, modification);
        }

        /// <summary>
        /// Sets the value of the attribute.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        /// <param name="value">The value.</param>
        public void Set(string attribute, IEnumerable<byte[]> value)
        {
            AddChanged(attribute, value.ToDirectoryModification(attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Sets the value of the attribute to null.
        /// </summary>
        /// <param name="attribute">The name of the attribute.</param>
        public void SetNull(string attribute)
        {
            AddChanged(attribute, ExtensionMethods.ToDirectoryModification(null, attribute, DirectoryAttributeOperation.Replace));
        }

        /// <summary>
        /// Returns any added, removed, or updated attributes and the value.
        /// </summary>
        ///<returns></returns>
        public IEnumerable<DirectoryAttributeModification> GetChangedAttributes()
        {
            if (_changedAttributes == null)
            {
                return new List<DirectoryAttributeModification>();
            }

            return _changedAttributes.Values;
        }

        private T? GetValueType<T>(string attribute) where T : struct 
        {
            T? value = null;
            var type = typeof(T);

            var directoryAttribute = Get(attribute);

            if (directoryAttribute != null)
            {
                object rawValue = null;
                try
                {
                    var valueArray = directoryAttribute.GetValues(typeof(string));
                    rawValue = valueArray.Length == 0
                                   ? null
                                   : valueArray[0];
                    value = rawValue == null
                        ? default(T?)
                        : (T)Convert.ChangeType(rawValue, type);
                }
                catch (Exception ex)
                {
                    ThrowFormatException(ex, attribute, rawValue, type);
                }
            }

            return value;
        }

        private void ThrowFormatException(Exception ex, string attribute, object value, Type conversionType)
        {
            var message = string.Format("Value '{0}' for attribute '{1}' caused {2} when trying to convert to '{3}' for {4}", 
                    value,
                    attribute,
                    ex.GetType().Name,
                    conversionType.Name,
                    DistinguishedName);

            ThrowFormatException(message, ex);
        }

        private static void ThrowFormatException(string message, Exception innerException)
        {
            throw new FormatException(message, innerException);
        }
    }
}
